package com.atguigu.gulimall.ware.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.enume.OrderStatusEnum;
import com.atguigu.common.exception.NotStockException;
import com.atguigu.common.to.es.SkuHasStockVo;
import com.atguigu.common.to.mq.OrderTo;
import com.atguigu.common.to.mq.StockDetailTo;
import com.atguigu.common.to.mq.StockLockedTo;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.ware.dao.WareSkuDao;
import com.atguigu.gulimall.ware.entity.WareOrderTaskDetailEntity;
import com.atguigu.gulimall.ware.entity.WareOrderTaskEntity;
import com.atguigu.gulimall.ware.entity.WareSkuEntity;
import com.atguigu.gulimall.ware.feign.OrderFeignService;
import com.atguigu.gulimall.ware.service.WareOrderTaskDetailService;
import com.atguigu.gulimall.ware.service.WareOrderTaskService;
import com.atguigu.gulimall.ware.service.WareSkuService;
import com.atguigu.gulimall.ware.vo.OrderItemVo;
import com.atguigu.gulimall.ware.vo.WareSkuLockVo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.Data;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


@RabbitListener(queues = "stock.release.stock.queue")  //监听这个队列中的消息
@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Resource
    WareSkuDao wareSkuDao;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    WareOrderTaskService wareOrderTaskService;

    @Autowired
    WareOrderTaskDetailService wareOrderTaskDetailService;

    @Autowired
    OrderFeignService orderFeignService;


    @Value("${myRabbitmq.MQConfig.eventExchange}")
    private String eventExchange;

    @Value("${myRabbitmq.MQConfig.routingKey}")
    private String routingKey;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                new QueryWrapper<WareSkuEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();
        /*
          wareId: 123,//仓库id
          skuId: 123//商品id
        */

        String  wareId = (String) params.get("wareId");
        if (!StringUtils.isEmpty(wareId)){
            wrapper.eq("ware_id", wareId);
        }

        String  skuId = (String) params.get("skuId");
        if (!StringUtils.isEmpty(skuId)){
            wrapper.eq("sku_id", skuId);
        }

        IPage<WareSkuEntity> page = this.page(new Query<WareSkuEntity>().getPage(params),wrapper);

        return new PageUtils(page);


    }

    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
        List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList(new QueryWrapper<WareSkuEntity>()
                .eq("sku_id", skuId)
                .eq("ware_id", wareId));

        if (wareSkuEntities == null || wareSkuEntities.size() == 0){
            WareSkuEntity wareSkuEntity = new WareSkuEntity();
            wareSkuEntity.setSkuId(skuId);
            wareSkuEntity.setStock(skuNum);
            wareSkuEntity.setWareId(wareId);
            wareSkuDao.insert(wareSkuEntity);
        }else {
            wareSkuDao.addStock(skuId,wareId,skuNum);
        }
    }

    @Override
    public List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds) {
        return skuIds.stream().map(id -> {
            SkuHasStockVo stockVo = new SkuHasStockVo();

            // 查询当前sku的总库存量
            stockVo.setSkuId(id);
            // 这里库存可能为null 要避免空指针异常
            Long skuStock = baseMapper.getSkuStock(id);
            stockVo.setHasStock(skuStock != null && skuStock > 0);
            return stockVo;
        }).collect(Collectors.toList());
    }


    @Transactional
    @Override
    public Boolean orderLockStock(WareSkuLockVo vo) {
        //0、按照下单的收货地址，找到一个就近仓库，锁定库存。

        // 锁定库存之前先保存订单任务 以便后来消息撤回
        WareOrderTaskEntity taskEntity = new WareOrderTaskEntity();
        taskEntity.setOrderSn(vo.getOrderSn());
        wareOrderTaskService.save(taskEntity);

        //1、找到每个商品在哪个仓库都有库存
        List<OrderItemVo> locks = vo.getLocks();
        List<SkuWareHasStock> wareHasStocks = locks.stream().map(item -> {
            SkuWareHasStock skuWareHasStock = new SkuWareHasStock();
            Long skuId = item.getSkuId();
            skuWareHasStock.setSkuId(skuId);
            //查询这个skuId的商品在哪里有库存
            // 查询这两个商品在哪有库存
            List<Long> wareIds = wareSkuDao.listWareIdHasSkuStock(skuId);
            skuWareHasStock.setWareId(wareIds);

            //要锁多少件
            skuWareHasStock.setNum(item.getCount());
            return skuWareHasStock;
        }).collect(Collectors.toList());

        for (SkuWareHasStock hasStock : wareHasStocks) {
            boolean skuStocked = false;
            Long skuId = hasStock.getSkuId();
            List<Long> wareIds = hasStock.getWareId();
            if (wareIds == null || wareIds.size() == 0) throw new NotStockException(skuId.toString());
            //如果每一个商品都锁定成功，将当前商品锁定了几件的工作单记录发给NQ
            //如果锁定失败。前面保存的工作单信息就回滚了。发送出去的消息，
            //即使要解锁记录，由于去数据库查不到id，所以就不用解锁
            for (Long wareId : wareIds) {
                //几号商品,在哪个仓库,要锁多少件
                Long count = wareSkuDao.lockSkuStock(skuId,wareId,hasStock.getNum());
                if (count == 1){
                    skuStocked = true;
                    //锁这个商品的库存成功,告诉MQ我锁好了
                    // TODO 告诉MQ库存锁定成功 一个订单锁定成功 消息队列就会有一个消息
                    WareOrderTaskDetailEntity detailEntity =
                            new WareOrderTaskDetailEntity(null,skuId,"",hasStock.getNum() ,taskEntity.getId(),wareId,1);
                    wareOrderTaskDetailService.save(detailEntity);

                    StockLockedTo stockLockedTo = new StockLockedTo();
                    stockLockedTo.setId(taskEntity.getId());

                    StockDetailTo stockDetailTo = new StockDetailTo();
                    //只发id不行，必须要防止回滚以后找不到数据
                    BeanUtils.copyProperties(detailEntity, stockDetailTo);
                    stockLockedTo.setDetailTo(stockDetailTo);

                    //发工作单ID+库存详情到MQ中
                    rabbitTemplate.convertAndSend(eventExchange,routingKey, stockLockedTo);
                    break;
                }
            }
            //如果有任意一个库存没锁成功,那么就代表订单无法创建,创建订单失败
            if (!skuStocked){
                throw new NotStockException(skuId.toString());
            }
        }

        return true;
    }


    //库存解锁的场景
    //1)、下订单成功，订单过期没有支付被系统自动取消、被用户手动取消。都要解锁库存
    //2)、下订单成功，库存锁定成功，接下来的业务调用失败，导致订单回滚。之前锁定的库存就要自动解锁。
    //3)、下订单失败. 库存锁定失败,库存业务回滚,详情单无内容,因此无法获取数据库信息,此时不进行操作即可

    //接收MQ的消息,来解锁库存
    @Override
    public void unlockStock(StockLockedTo to) throws RuntimeException {
        StockDetailTo detailTo = to.getDetailTo();
        Long detailId = detailTo.getId();

        //开始解锁:
        //查询数据库关于这个订单的锁定库存信息
        //如果有,说明是上面情景的1,2两个之一,此时就通过代码再去判断订单,然后考虑是否去解锁库存(补偿)
        //如果没有,说明锁库存都失败了,因此也就无需操作
        WareOrderTaskDetailEntity wotDetailEntity = wareOrderTaskDetailService.getById(detailId);
        if (wotDetailEntity != null) {
            //如果不为空,只能证明库存没有问题,还需要继续判断订单
                //如果订单为空---> 此时说明其他服务出现了问题,此时必须解锁!
                //如果订单不为空--->则继续查看订单状态
                    //订单状态:以取消-->真正解锁库存
                    //        没取消-->不能解锁库存

            Long taskId = to.getId();  //获取库存工作单的Id
            WareOrderTaskEntity taskEntity = wareOrderTaskService.getById(taskId);
            String orderSn = taskEntity.getOrderSn();  //获取订单号

            R r = orderFeignService.getOrderStatus(orderSn);
            if (r.getCode() == 0){
                //这里直接使用了OrderTo,没有再新建一个VO了
                OrderTo data = r.getData(new TypeReference<OrderTo>() {});
                //如果一查发现订单不存在,说明其他服务出现了问题,订单回滚了,此时必须解锁!
                //或者如果订单状态是已取消,才可以去解锁库存
                if(data == null || data.getStatus().equals(OrderStatusEnum.CANCLED.getCode())){
                    //当库存详情状态为已锁定,才有必要去解锁库存
                    if (wotDetailEntity.getLockStatus() == 1){
                        unLock(detailTo.getSkuId(), detailTo.getWareId(), detailTo.getSkuNum(), detailId);
                    }
                }
                //否则啥也不做
            }else {
                //如果调用失败,抛出异常,让消息放回队列中
                throw new RuntimeException("远程服务调用失败!");
            }
        } else {
            //无需解锁,因为库存逻辑本身就出现了问题!

        }
    }

    //处理关单后发过来的解锁库存的消息!
    //放在订单系统卡顿,导致库存永久性无法解锁
    @Transactional
    @Override
    public void unlockStock(OrderTo orderTo) {
        String orderSn = orderTo.getOrderSn();
        //必须要查询一下订单的最新状态,否则可能出现重复解锁的情况!
        WareOrderTaskEntity taskEntity = wareOrderTaskService.getOrderTaskByOrderSn(orderSn);
        //按照工作单,找到所有没有解锁的库存,来进行解锁!状态为1等于已锁定
        Long taskEntityId = taskEntity.getId();
        List<WareOrderTaskDetailEntity> entities = wareOrderTaskDetailService
                .list(new QueryWrapper<WareOrderTaskDetailEntity>()
                        .eq("task_id", taskEntityId)
                        .eq("lock_status", 1));
        for (WareOrderTaskDetailEntity entity : entities) {
            unLock(entity.getSkuId(), entity.getWareId(), entity.getSkuNum(), entity.getId());
        }

    }

    /**
     * 解锁库存
     */
    private void unLock(Long skuId,Long wareId, Integer num, Long taskDetailId){
        // 更新库存
        wareSkuDao.unlockStock(skuId, wareId, num);
        // 更新库存工作单的状态
        WareOrderTaskDetailEntity detailEntity = new WareOrderTaskDetailEntity();
        detailEntity.setId(taskDetailId);
        detailEntity.setLockStatus(2);
        wareOrderTaskDetailService.updateById(detailEntity);
    }



    @Data
    class SkuWareHasStock{
        private Integer num;
        private Long skuId;
        private List<Long> wareId;
    }

}